package com.unism.unisdk.core.transport;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.unism.unisdk.api.dingding.model.UnismResponse;
import com.unism.unisdk.common.OpenAPI;
import com.unism.unisdk.common.SdkInitException;
import com.unism.unisdk.common.ServiceException;
import com.unism.unisdk.common.enumObj.HttpMethod;
import com.unism.unisdk.common.enumObj.Location;
import com.unism.unisdk.common.enumObj.ParamAttr;
import com.unism.unisdk.core.DefaultConfig;
import com.unism.unisdk.core.transport.ApiAttr.ApiParam;
import com.unism.unisdk.core.transport.action.http.HttpRequestData;
import com.unism.unisdk.core.transport.invoke.RemoteInvoke;
import com.unism.unisdk.core.utils.StringUtils;
/**
 * 接口服务代理类
 * 实现具体方法调用逻辑
 * @author liuc
 */
public class OpenServiceProxy implements InvocationHandler {
	private static final Logger mainLogger = LoggerFactory.getLogger(OpenServiceProxy.class);
	private static final Logger invokeLogger = LoggerFactory.getLogger("HTTP_INVOKE_LOGGER");
	private Map<String, ApiAttr> apiAttrMap;
	private DefaultConfig defaultConfig;
	private RemoteInvoke remoteInvoke;

	public OpenServiceProxy(Set<Class<?>> apiServices, DefaultConfig defaultConfig, RemoteInvoke remoteInvoke)
			throws SdkInitException {
		
		if (apiServices.size() == 0) {
			throw new SdkInitException("初始化失败,待初始化的API不能为空");
		}
		this.defaultConfig = defaultConfig;
		
		this.apiAttrMap = new HashMap<String,ApiAttr>();
		//解析api参数、地址、请求类型等配置信息
		parseApi(apiServices, defaultConfig.getApiDomain());
        //检查请求配置的（此处有问题）
		checkCustomHttpConfig();

		this.remoteInvoke = remoteInvoke;
	}

	private void checkCustomHttpConfig() throws SdkInitException {
		if (this.defaultConfig.getCustomHttpConfig() != null)
			for (String key : this.defaultConfig.getCustomHttpConfig().keySet())
				if (!this.apiAttrMap.containsKey(key))
					throw new SdkInitException(new StringBuilder().append("初始化失败,为API[").append(key)
							.append("]设置网络超时时间参数无效,因为该API不存在").toString());
	}

	private void initFullUrl(ApiAttr apiAttr, Method apiMethod, OpenAPI openAPI, String openApiDomain) {
		String fullUrl = new StringBuilder()
				.append(openApiDomain.endsWith("/") ? openApiDomain.substring(0, openApiDomain.length() - 1)
						: openApiDomain)
				.append(openAPI.uriPath().startsWith("/") ? openAPI.uriPath()
						: new StringBuilder().append("/").append(openAPI.uriPath()).toString())
				.toString();
		apiAttr.setUrl(fullUrl);
	}

	private void initApiParams(ApiAttr apiAttr, Method apiMethod, Class<?> clazz) throws SdkInitException {
		List<ApiParam> apiParams = new ArrayList<ApiParam>();
		int emptyParamKeyTotal = 0;
		int fileParamTotal = 0;
		for (int i = 0; i < apiMethod.getParameterAnnotations().length; i++) {
			Annotation[] paramAnnotations = apiMethod.getParameterAnnotations()[i];
			if (paramAnnotations.length == 0) {
				throw new SdkInitException(new StringBuilder().append("初始化失败,API[").append(clazz.getCanonicalName())
						.append(".").append(apiMethod.getName()).append("]中的参数需要设置注解[ParamAttr]").toString());
			}

			ApiAttr.ApiParam apiParam = new ApiAttr.ApiParam();
			apiParam.setIndex(i);
			boolean hasParamAttr = false;

			for (Annotation annotation : paramAnnotations) {
				if ((annotation instanceof ParamAttr)) {
					ParamAttr paramAttr = (ParamAttr) annotation;
					if (paramAttr.location() == Location.URL) {
						if (StringUtils.isEmpty(paramAttr.paramName())) {
							throw new SdkInitException(new StringBuilder().append("初始化失败,API[")
									.append(clazz.getCanonicalName()).append(".").append(apiMethod.getName())
									.append("]中的参数设置的注解[ParamAttr]不正确,对于Location.URL类型的参数,不允许设置key为空").toString());
						}
					} else if (paramAttr.location() == Location.JSON_CONTENT) {
						if (StringUtils.isEmpty(paramAttr.paramName()))
							emptyParamKeyTotal++;
					} else if (paramAttr.location() == Location.FILE) {
						fileParamTotal++;
						if (File.class != apiMethod.getParameterTypes()[i]) {
							throw new SdkInitException(new StringBuilder().append("初始化失败,API[")
									.append(clazz.getCanonicalName()).append(".").append(apiMethod.getName())
									.append("]中的参数设置的注解[ParamAttr]不正确,对于Location.FILE类型的参数,传入的参数类型必须为File").toString());
						}

						if (StringUtils.isEmpty(paramAttr.paramName())) {
							throw new SdkInitException(new StringBuilder().append("初始化失败,API[")
									.append(clazz.getCanonicalName()).append(".").append(apiMethod.getName())
									.append("]中的参数设置的注解[ParamAttr]不正确,对于Location.FILE类型的参数,不允许设置key为空").toString());
						}

					}
					apiParam.setKey(paramAttr.paramName());
					apiParam.setLocation(paramAttr.location());
					hasParamAttr = true;
					break;
				}
			}
			if (!hasParamAttr) {
				throw new SdkInitException(new StringBuilder().append("初始化失败,API[").append(clazz.getCanonicalName())
						.append(".").append(apiMethod.getName()).append("]中的参数需要设置注解[ParamAttr]").toString());
			}

			if (emptyParamKeyTotal > 1) {
				throw new SdkInitException(new StringBuilder().append("初始化失败,API[").append(clazz.getCanonicalName())
						.append(".").append(apiMethod.getName())
						.append("]中的参数设置的注解[ParamAttr]不正确,对于Location.JSON_CONTENT类型的参数,最多只允许有一个参数的key为空").toString());
			}

			if (fileParamTotal > 1) {
				throw new SdkInitException(new StringBuilder().append("初始化失败,API[").append(clazz.getCanonicalName())
						.append(".").append(apiMethod.getName())
						.append("]中的参数设置的注解[ParamAttr]不正确,对于Location.FILE类型的参数,最多只允许有一个").toString());
			}

			apiParams.add(apiParam);
		}
		apiAttr.setApiParams(apiParams);
	}
	private void initReturnClass(ApiAttr apiAttr, Method apiMethod, Class<?> clazz) throws SdkInitException {
		ApiAttr.ReturnClass returnClass = new ApiAttr.ReturnClass();

		Type genericReturnType = apiMethod.getGenericReturnType();
		if (ParameterizedType.class.isAssignableFrom(genericReturnType.getClass())) {
			Type rawType = ((ParameterizedType) genericReturnType).getRawType();
			if (!UnismResponse.class.equals(rawType)) {
				throw new SdkInitException(new StringBuilder().append("初始化失败,API[").append(clazz.getCanonicalName())
						.append(".").append(apiMethod.getName()).append("]中的返回类型定义为泛型,目前仅支持[UnismResponse<T>]").toString());
			}
			returnClass.setGeneric(true);
			Type argumentsType =((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
			if(argumentsType!=null){
				returnClass.setClazz(null);
				returnClass.setType(genericReturnType);
			}
		} else {
			returnClass.setGeneric(false);
			returnClass.setClazz((Class<?>) genericReturnType);
		}
		apiAttr.setReturnClass(returnClass);
	}

	private void initRequestConfig(ApiAttr apiAttr, OpenAPI openAPI) throws SdkInitException {
		int requestTimeout = this.defaultConfig.getGlobalHttpConfig().getRequestTimeout();
		if (openAPI.requestTimeout() != 60000) {
			requestTimeout = openAPI.requestTimeout();
		}

		if (this.defaultConfig.getCustomHttpConfig() != null) {
			DefaultConfig.HttpConfig customHttpConfig = (DefaultConfig.HttpConfig) this.defaultConfig
					.getCustomHttpConfig().get(openAPI.uriPath());
			if (customHttpConfig != null) {
				requestTimeout = customHttpConfig.getRequestTimeout();
			}
		}
		apiAttr.setRequestTimeout(requestTimeout);
	}
   /**
    * 根据配置信息和api接口中的注解自动拼组相应的url地址
    * @param apiServices  实现@openService注解的类的集合(去重)
    * @param openApiDomain  基础配置中的域名端口信息===>http://127.0.0.1:8080
    * @throws SdkInitException  
    */
	private void parseApi(Set<Class<?>> apiServices, String openApiDomain) throws SdkInitException {
		for (Class<?> clazz : apiServices) {
			for (Method apiMethod : clazz.getMethods()) {
				ApiAttr apiAttr = new ApiAttr();
				OpenAPI openAPI = (OpenAPI) apiMethod.getAnnotation(OpenAPI.class);

				if (this.apiAttrMap.get(openAPI.uriPath()) != null) {
					throw new SdkInitException(new StringBuilder().append("初始化失败,API[").append(clazz.getCanonicalName())
							.append(".").append(apiMethod.getName()).append("]中的注解[OpenAPI]参数[urlPath]的值[")
							.append(openAPI.uriPath()).append("]重复了").toString());
				}
                //解析拼组url
				initFullUrl(apiAttr, apiMethod, openAPI, openApiDomain);
                //设置相应的http请求方法
				apiAttr.setHttpMethod(openAPI.httpMethod());
                //初始化参数和参数类型及位置
				initApiParams(apiAttr, apiMethod, clazz);
                //初始化接口返回结果类型
				initReturnClass(apiAttr, apiMethod, clazz);
                //初始化请求配置（请求超时时间、）
				initRequestConfig(apiAttr, openAPI);
                //初始化jsonkey
				apiAttr.setResultJsonKey(openAPI.resultJsonKey());
               
				this.apiAttrMap.put(openAPI.uriPath(), apiAttr);
			}
		}
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Long currTime = System.currentTimeMillis();
		String requestId = StringUtils.generateRequestId();//形成唯一uuid
		String api = "";
		String fullUrl = "";//全路径http://127.0.0.1:8080/data/command/operateOpen
		Long rt = 0L;
		Integer httpCode = 0;
		String requestbody = "";
		String responseBody = "";
		Throwable throwable = null;
		Long startExcuteTime = 0L;
		Long expectedQuitQueueTime = 0L;
		Long actualQuitQueueTime = 0L;
		Long endExcuteTime = 0L;
		try {
			api = new StringBuilder().append(method.getDeclaringClass().getSimpleName()).append(".")
					.append(method.getName()).toString();

			OpenAPI openAPI = (OpenAPI) method.getAnnotation(OpenAPI.class);
			
			if (openAPI == null) {
				throw new Exception(
						new StringBuilder().append("API方法[").append(api).append("]上需要有[OpenAPI]注解").toString());
			}
			
			if ((openAPI.uriPath() == null) || (openAPI.uriPath().isEmpty())) {
				throw new Exception(new StringBuilder().append("API方法[").append(api)
						.append("]的注解[OpenAPI]参数[urlPath]不能为空").toString());
			}

			ApiAttr apiAttr = (ApiAttr) this.apiAttrMap.get(openAPI.uriPath());
			if (apiAttr == null) {
				throw new Exception(new StringBuilder().append("调用的API[").append(openAPI.uriPath())
						.append("]没有初始化,无法正常使用").toString());
			}
			
			if (invokeLogger.isDebugEnabled()) {
				Object[] logArgs = { requestId, JSON.toJSONString(apiAttr) };
				StringBuilder sb = new StringBuilder();
				sb.append("reqId={}");
				sb.append("\t");
				sb.append("apiAttr={}");

				invokeLogger.debug(sb.toString(), logArgs);
			}
            //一般情况不存在
			if (apiAttr.getApiParams().size() != args.length) {
				throw new Exception(new StringBuilder().append("调用的API[").append(openAPI.uriPath())
						.append("]的参数个数与初始化时得到的参数个数不匹配,API无法正常使用").toString());
			}
			String body = null;
			Object bodyObj=null;
			File finalPostFile = null;
			String finalPostFileKey = null;
			StringBuilder urlParam = new StringBuilder("?");
			StringBuilder fromParam = new StringBuilder("");
			int urlParamNum = 0;
			
			//根据注解拼组请求参数
			for (Integer i = 0; i < apiAttr.getApiParams().size(); i++) {
				ApiAttr.ApiParam apiParam = apiAttr.getApiParams().get(i);
				switch(apiParam.getLocation()){
				    //如果参数在rul中形如 ?age=12&name=liuc
				    case URL:
				    	if(args[i] != null){
							urlParamNum++;
							if(urlParamNum > 1){
								urlParam.append("&");
							}
							urlParam.append(apiParam.getKey());
							urlParam.append("=");
							urlParam.append(args[i]);
						}
				        break;
					case FILE:
						finalPostFile = (File) args[i];
						finalPostFileKey = apiParam.getKey();
						if ((finalPostFile == null) || (StringUtils.isEmpty(finalPostFileKey))) {
							throw new Exception(new StringBuilder().append("调用的API[").append(openAPI.uriPath())
									.append("]的参数注解ParamAttr使用了 Location.FILE,但实际传入的对象为null或ParamKey为空,API无法正常使用")
									.toString());
						}
						break;
				    //如果参数在body中,采用通用表单提交的方式
					case FROM_CONTENT:
						urlParamNum++;
						if(urlParamNum > 1){
							fromParam.append("&");
						}
						fromParam.append(apiParam.getKey());
						fromParam.append("=");
						fromParam.append(args[i]);
						break;
					case JSON_CONTENT:
					case XML_CONTENT:
						bodyObj = args[i];
						break;
					default:
						break;
				}
			}
			//全路径+rul参数
			fullUrl = urlParam.insert(0, apiAttr.getUrl()).toString();
            
			body = bodyObj == null ? fromParam.toString(): JSON.toJSONString(bodyObj);

			HttpRequestData requestData = new HttpRequestData();
			requestData.setApiAttr(apiAttr);
			requestData.setFullUrl(fullUrl);
			requestData.setRequestId(requestId);
			requestData.setApiInfo(api);
			requestData.setBody(body);
			requestData.setFileKey(finalPostFileKey);
			requestData.setFile(finalPostFile);
			requestData.setMethod(apiAttr.getHttpMethod());

			OpenResponse openResponse = this.remoteInvoke.execute(requestData);
			startExcuteTime = requestData.getStartExecuteTime();
			expectedQuitQueueTime = requestData.getExpectedQuitQueueTime();
			actualQuitQueueTime = requestData.getActualQuitQueueTime();
			endExcuteTime = requestData.getEndExecuteTime();
			if (openResponse == null) {
				throw new Exception("SDK的返回值为null,请联系pdf@unism.com.cn");
			}
			if (openResponse.getThrowable() != null) {
				throw openResponse.getThrowable();
			}

			httpCode = openResponse.getHttpCode();
			responseBody = openResponse.getResult();
			if (httpCode != 200) {
				throw new Exception(new StringBuilder().append("API方法[").append(api).append("]的 HTTP 响应码不是200,值为")
						.append(openResponse.getHttpCode()).append(",请联系pdf@unism.com.cn").toString());
			}
			responseBody = openResponse.getResult();
			Gson gson = new Gson();
			Object result=null;
			if(apiAttr.getReturnClass().isGeneric()){
				result =gson.fromJson(responseBody, apiAttr.getReturnClass().getType());
			}else{
				result = gson.fromJson(responseBody, apiAttr.getReturnClass().getClazz());
			}
			return result;//.getObject(apiAttr.getResultJsonKey(), apiAttr.getReturnClass().getClazz());
		} catch (Throwable th) {
			throwable = th;
			throw th;
		} finally {
			long endTime = System.currentTimeMillis();
			rt = endTime - currTime;

			Object[] logArgs = { requestId, api, fullUrl, Long.valueOf(rt), Integer.valueOf(httpCode),requestbody,
					responseBody, throwable == null ? null : throwable.toString() };
			StringBuilder sb = new StringBuilder();
			sb.append("reqId={}");
			sb.append("\t");
			sb.append("api={}");
			sb.append("\t");
			sb.append("url={}");
			sb.append("\t");
			sb.append("rt={}ms");
			sb.append("\t");

			if (actualQuitQueueTime.longValue() == 0L) {
				sb.append(new StringBuilder().append("start->exe_start=").append(startExcuteTime.longValue() - currTime)
						.append("ms").toString());
				sb.append("\t");
				sb.append(new StringBuilder().append("exe_start->exe_end=")
						.append(endExcuteTime.longValue() - startExcuteTime.longValue()).append("ms").toString());
				sb.append("\t");
				sb.append(new StringBuilder().append("exe_end->end=").append(endTime - endExcuteTime.longValue())
						.append("ms").toString());
				sb.append("\t");
			} else {
				sb.append(new StringBuilder().append("start->exe_start=").append(startExcuteTime.longValue() - currTime)
						.append("ms").toString());
				sb.append("\t");
				sb.append(new StringBuilder().append("exe_start->expected_quit_queue=")
						.append(expectedQuitQueueTime.longValue() - startExcuteTime.longValue()).append("ms")
						.toString());
				sb.append("\t");
				sb.append(new StringBuilder().append("expected_quit_queue->actual_quit_queue=")
						.append(actualQuitQueueTime.longValue() - expectedQuitQueueTime.longValue()).append("ms")
						.toString());

				sb.append("\t");
				sb.append(new StringBuilder().append("actual_quit_queue->exe_end=")
						.append(endExcuteTime.longValue() - actualQuitQueueTime.longValue()).append("ms").toString());
				sb.append("\t");
				sb.append(new StringBuilder().append("exe_end->end=").append(endTime - endExcuteTime.longValue())
						.append("ms").toString());
				sb.append("\t");
			}

			sb.append("http_code={}");
			sb.append("\t");
			sb.append("post_json_content={}");
			sb.append("\t");
			sb.append("response_json_content={}");
			if (throwable != null) {
				sb.append("\t");
				sb.append("throwable={}");
			}

			if (httpCode == 200)
				invokeLogger.info(sb.toString(), logArgs);
			else
				invokeLogger.warn(sb.toString(), logArgs);
		}
	}
}